import traceback
from threading import Thread
from queue import Queue, Empty
from copy import copy, deepcopy
from collections import defaultdict
from typing import Optional
from datetime import datetime, timedelta, time

from vnpy.event import Event, EventEngine
from vnpy.trader.engine import BaseEngine, MainEngine
from vnpy.trader.constant import Exchange
from vnpy.trader.object import (
    SubscribeRequest,
    TickData,
    BarData,
    ContractData
)
from vnpy.trader.event import EVENT_TICK, EVENT_CONTRACT, EVENT_TIMER
from vnpy.trader.utility import load_json, save_json, CurrentBarGenerator
from vnpy.trader.database import BaseDatabase, get_database, DB_TZ
from vnpy_spreadtrading.base import EVENT_SPREAD_DATA, SpreadItem
from vnpy.trader.constant import Interval


APP_NAME = "DataRecorder"

EVENT_RECORDER_LOG = "eRecorderLog"
EVENT_RECORDER_UPDATE = "eRecorderUpdate"
EVENT_RECORDER_EXCEPTION = "eRecorderException"

#注释符号：&sys&-为孙远山阅读代码注释
#&sys& 本文件是数据记录模块的引擎（内核）
class RecorderEngine(BaseEngine):#&sys&引擎是一个类,BaseEngine是父类
    """
    For running data recorder.
    """

    setting_filename: str = "data_recorder_setting.json" #&sys& 这个文件路径在（windows）："C:\Users\用户\.vntrader\data_recorder_setting.json"
    '''#&sys&记录内容为
    {
        "tick": {
            "ag2412.SHFE": {
                "symbol": "ag2412",
                "exchange": "SHFE",
                "gateway_name": "CTP"
            },
            "RM409.CZCE": {
                "symbol": "RM409",
                "exchange": "CZCE",
                "gateway_name": "CTP"
            },
            "m2409.DCE": {
                "symbol": "m2409",
                "exchange": "DCE",
                "gateway_name": "CTP"
            }
        },
        "bar": {
            "ag2412.SHFE": {
                "symbol": "ag2412",
                "exchange": "SHFE",
                "gateway_name": "CTP"
            },
            "RM409.CZCE": {
                "symbol": "RM409",
                "exchange": "CZCE",
                "gateway_name": "CTP"
            },
            "m2409.DCE": {
                "symbol": "m2409",
                "exchange": "DCE",
                "gateway_name": "CTP"
            }
        }
    }
    '''
    def __init__(self, main_engine: MainEngine, event_engine: EventEngine) -> None:
        """"""
        super().__init__(main_engine, event_engine, APP_NAME)

        self.queue: Queue = Queue()
        self.thread: Thread = Thread(target=self.run)
        self.active: bool = False

        self.tick_recordings: dict[str, dict] = {}
        self.bar_recordings: dict[str, dict] = {}
        self.bar_generators: dict[str, dict[str, CurrentBarGenerator]] = {}#这个是bg的字典列表，存储<合约id，bg>
            #将dict的第二个字段也改为dict
        self.timer_count: int = 0
        self.timer_interval: int = 10

        self.ticks: dict[str, list[TickData]] = defaultdict(list)
        self.bars: dict[str, list[BarData]] = defaultdict(list)

        self.quotation_boards: dict[str, TickData] = {}     # 行情看板数据结构

        self.current_bar_id_dict: dict[str, dict[str, int]] = {}#当前K线字典

        self.filter_dt: datetime = datetime.now(DB_TZ)      # Tick数据过滤的时间戳
        self.filter_window: int = 60                        # Tick数据过滤的时间窗口，默认60秒
        self.filter_delta: timedelta = None                 # Tick数据过滤的时间偏差对象

        self.database: BaseDatabase = get_database()

        self.load_setting()
        self.register_event()
        self.start()
        self.put_event()

    def load_setting(self) -> None:
        """"""
        setting: dict = load_json(self.setting_filename)
        self.tick_recordings = setting.get("tick", {})
        self.bar_recordings = setting.get("bar", {})

        self.filter_window = setting.get("filter_window", 60)
        self.filter_delta = timedelta(seconds=self.filter_window)

    def save_setting(self) -> None:
        """"""
        setting: dict = {
            "tick": self.tick_recordings,
            "bar": self.bar_recordings
        }
        save_json(self.setting_filename, setting)

    def run(self) -> None:
        """"""
        print("RecorderEngine.run")
        while self.active:
            try:
                task: object = self.queue.get(timeout=1)
                task_type, data = task

                if task_type == "tick":
                    #print("self.database.save_tick_data(data, stream=True)")
                    self.database.save_tick_data(data, stream=True)
                elif task_type == "bar":
                    #print("self.database.save_bar_data(data, stream=True)")
                    self.database.save_bar_data(data, stream=True)#就是这里阻塞住了

            except Empty:
                #print("Empty queue")
                continue

            except Exception:
                traceback.print_exc()
                self.active = False

                info = traceback.format_exc()
                event: Event = Event(EVENT_RECORDER_EXCEPTION, info)
                self.event_engine.put(event)

    def close(self) -> None:
        """"""
        self.active = False

        if self.thread.is_alive():
            self.thread.join()

    def start(self) -> None:
        """"""
        self.active = True
        self.thread.start()

    def add_bar_recording(self, vt_symbol: str) -> None:
        """"""
        if vt_symbol in self.bar_recordings:
            self.write_log(f"已在K线记录列表中：{vt_symbol}")
            return

        if Exchange.LOCAL.value not in vt_symbol:
            contract: Optional[ContractData] = self.main_engine.get_contract(vt_symbol)
            if not contract:
                self.write_log(f"找不到合约：{vt_symbol}")
                return

            self.bar_recordings[vt_symbol] = {
                "symbol": contract.symbol,
                "exchange": contract.exchange.value,
                "gateway_name": contract.gateway_name
            }

            self.subscribe(contract)
        else:
            self.bar_recordings[vt_symbol] = {}

        self.save_setting()
        self.put_event()

        self.write_log(f"添加K线记录成功：{vt_symbol}")

    def add_tick_recording(self, vt_symbol: str) -> None:
        """"""
        if vt_symbol in self.tick_recordings:
            self.write_log(f"已在Tick记录列表中：{vt_symbol}")
            return

        # For normal contract
        if Exchange.LOCAL.value not in vt_symbol:
            contract: Optional[ContractData] = self.main_engine.get_contract(vt_symbol)
            if not contract:
                self.write_log(f"找不到合约：{vt_symbol}")
                return

            self.tick_recordings[vt_symbol] = {
                "symbol": contract.symbol,
                "exchange": contract.exchange.value,
                "gateway_name": contract.gateway_name
            }

            self.subscribe(contract)
        # No need to subscribe for spread data
        else:
            self.tick_recordings[vt_symbol] = {}

        self.save_setting()
        self.put_event()

        self.write_log(f"添加Tick记录成功：{vt_symbol}")

    def remove_bar_recording(self, vt_symbol: str) -> None:
        """"""
        if vt_symbol not in self.bar_recordings:
            self.write_log(f"不在K线记录列表中：{vt_symbol}")
            return

        self.bar_recordings.pop(vt_symbol)
        self.save_setting()
        self.put_event()

        self.write_log(f"移除K线记录成功：{vt_symbol}")

    def remove_tick_recording(self, vt_symbol: str) -> None:
        """"""
        if vt_symbol not in self.tick_recordings:
            self.write_log(f"不在Tick记录列表中：{vt_symbol}")
            return

        self.tick_recordings.pop(vt_symbol)
        self.save_setting()
        self.put_event()

        self.write_log(f"移除Tick记录成功：{vt_symbol}")

    def register_event(self) -> None:
        """"""
        self.event_engine.register(EVENT_TIMER, self.process_timer_event)
        self.event_engine.register(EVENT_TICK, self.process_tick_event)
        self.event_engine.register(EVENT_CONTRACT, self.process_contract_event)
        self.event_engine.register(EVENT_SPREAD_DATA, self.process_spread_event)

    def update_tick(self, tick: TickData) -> None:
        """"""
        # 过滤偏离本地时间戳过大的Tick数据
        tick_delta: timedelta = abs(tick.datetime - self.filter_dt)
        if abs(tick_delta) >= self.filter_delta:
            return #时间戳偏离过大的Tick就直接返回了，就跳过了记录的环节

        if tick.vt_symbol in self.tick_recordings:
            #如果tick的symolID在Tick记录（设置）中，那么提交给record_Tick函数，如果不在那么自然不会执行下面的代码
            self.record_tick(copy(tick))

        #更新以下行情看板
        self.quotation_boards[tick.vt_symbol]=tick#这一句话就是如果有就更新，如果没有就插入

        if tick.vt_symbol in self.bar_recordings:
            #如果tick的symolID在Bar记录（设置）中，那么提交给的是BarGenerator
            #这里更换为CurrentBarGenerator
            bg_dict: dict[str,CurrentBarGenerator] = self.get_bar_generator(tick.vt_symbol)#bg注释为bg类型，取bg，以tick的合约代码
            #先取出BarGenerator以Tick的SymbolID
            #print("")
            bg_dict['1m'].update_tick(copy(tick))#用bg的update_tick(copy(tick))//用的是tick的副本
            #print("bg_dict['1m'].update_tick(copy(tick))")
            bg_dict['5m'].update_tick(copy(tick))
            #print("bg_dict['5m'].update_tick(copy(tick))")
            bg_dict['15m'].update_tick(copy(tick))
            #print("bg_dict['15m'].update_tick(copy(tick))")
            bg_dict['30m'].update_tick(copy(tick))
            #print("bg_dict['30m'].update_tick(copy(tick))")
            bg_dict['1h'].update_tick(copy(tick))
            #print("bg_dict['1h'].update_tick(copy(tick))")
            bg_dict['1d'].update_tick(copy(tick))
            #print("bg_dict['1d'].update_tick(copy(tick))")
            #从日志可以看到，这部分代码是执行了的


    def process_timer_event(self, event: Event) -> None:
        """"""
        self.filter_dt = datetime.now(DB_TZ)

        self.timer_count += 1
        if self.timer_count < self.timer_interval:
            return
        self.timer_count = 0

        print("RecordEngine:process_timer_event")
        self.database.update_quotation_board(self.quotation_boards)
        for bars in self.bars.values():
            self.queue.put(("bar", bars))
        self.bars.clear()

        for ticks in self.ticks.values():
            self.queue.put(("tick", ticks))
        self.ticks.clear()

    def process_tick_event(self, event: Event) -> None:
        """"""
        tick: TickData = event.data
        self.update_tick(tick)

    def process_contract_event(self, event: Event) -> None:
        """"""
        contract: ContractData = event.data
        vt_symbol: str = contract.vt_symbol

        if (vt_symbol in self.tick_recordings or vt_symbol in self.bar_recordings):
            self.subscribe(contract)

    def process_spread_event(self, event: Event) -> None:
        """"""
        spread_item: SpreadItem = event.data
        tick: TickData = TickData(
            symbol=spread_item.name,
            exchange=Exchange.LOCAL,
            datetime=spread_item.datetime,
            name=spread_item.name,
            last_price=(spread_item.bid_price + spread_item.ask_price) / 2,
            bid_price_1=spread_item.bid_price,
            ask_price_1=spread_item.ask_price,
            bid_volume_1=spread_item.bid_volume,
            ask_volume_1=spread_item.ask_volume,
            localtime=spread_item.datetime,
            gateway_name="SPREAD"
        )

        # Filter not inited spread data
        if tick.datetime:
            self.update_tick(tick)

    def write_log(self, msg: str) -> None:
        """"""
        event: Event = Event(
            EVENT_RECORDER_LOG,
            msg
        )
        self.event_engine.put(event)

    def put_event(self) -> None:
        """"""
        #对Tick的SymbolID进行排序
        tick_symbols: list[str] = list(self.tick_recordings.keys())
        tick_symbols.sort()
        #对Bar的SymbolID进行排序
        bar_symbols: list[str] = list(self.bar_recordings.keys())
        bar_symbols.sort()
        #将两个symbolId打包成一个dict
        data: dict = {
            "tick": tick_symbols,
            "bar": bar_symbols
        }
        #然后注册一个事件这个事件叫做 EVENT_RECORDER_UPDATE,并且带着上面的这个数据包
        event: Event = Event(
            EVENT_RECORDER_UPDATE,
            data
        )
        #把它扔给event引擎的put
        self.event_engine.put(event)

    def record_tick(self, tick: TickData) -> None:
        """"""
        self.ticks[tick.vt_symbol].append(tick)

    def record_bar(self, bar: BarData) -> None: #这个就是传给bargenerator的回调函数
        """"""
        #print("RecorderEngine:record_bar")
        self.bars[bar.vt_symbol].append(bar)
        #走完的K线推入队列，另一个线程触发则写入数据库
        #未走完的K线既没有推入队列，就更不可能写入数据库了
        #这里使用的是“生产者-消费者模式”
        #在没有推入队列之前只是一个bar对象

    ### 以下部分为解决当前K线不显示问题的代码⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇
    def insert_current_bar(self, bar: BarData) -> int:
        #deepcopy会递归复制对象的各项内容
        id = self.database.insert_current_bar(deepcopy(bar))
        self.set_current_bar_id_dict_id(str(bar.symbol), str(bar.interval), id);
        return id

    def update_current_bar(self, bar: BarData) -> int:
        id = self.get_current_bar_id_dict_id(str(bar.symbol), str(bar.interval))
        if id != -1:
            id = self.database.update_current_bar(id, deepcopy(bar))
            self.set_current_bar_id_dict_id(str(bar.symbol), str(bar.interval), id);
        else:
            print("更新：函数返回-1")
        return id

    def delete_current_bar(self, bar: BarData) -> bool:
        #packages\vnpy_datarecorder\engine.py", line 400, in delete_current_bar
        #id = self.get_current_bar_id_dict_id(str(bar.symbol), str(bar.interval))
        #AttributeError: 'NoneType' object has no attribute 'symbol'
        #这个错误表示，在我们从数据库中删除记录之前，bar对象已经在内存中被删除了
        id = self.get_current_bar_id_dict_id(str(bar.symbol), str(bar.interval))
        if id != -1:
            if self.database.delete_current_bar(id):
                self.set_current_bar_id_dict_id(str(bar.symbol), str(bar.interval), -1);
                return True
            else:
                print("删除失败")
                return False
        else:
            print("删除：函数返回-1")
            return False

    def set_current_bar_id_dict_id(self, symbol:str, interval:str, id: int) -> bool:

        if symbol in self.current_bar_id_dict:
            self.current_bar_id_dict[symbol][interval]=id
        else:#如果不存在这个合约的，就更不可能存在各时间周期的
            print("set_current_bar_id_dict_id:创建"+symbol)
            self.current_bar_id_dict[symbol]={} #创建外层字典
            #创建内层字典
            self.current_bar_id_dict[symbol][Interval.MINUTE]=-1
            self.current_bar_id_dict[symbol][Interval.MINUTE_5]=-1
            self.current_bar_id_dict[symbol][Interval.MINUTE_15]=-1
            self.current_bar_id_dict[symbol][Interval.MINUTE_30]=-1
            self.current_bar_id_dict[symbol][Interval.HOUR]=-1
            self.current_bar_id_dict[symbol][Interval.DAILY]=-1
            # 现在进行赋值
            self.current_bar_id_dict[symbol][interval] = id
        return True

    def get_current_bar_id_dict_id(self, symbol:str, interval:str) -> int:
        if symbol in self.current_bar_id_dict:
            if interval in self.current_bar_id_dict[symbol]:
                return self.current_bar_id_dict[symbol][interval]
            else:
                print("set_current_bar_id_dict_id:创建" + symbol+" "+interval)
                self.current_bar_id_dict[symbol][interval]=-1
                return -1
        else:#如果不存在这个合约
            print("set_current_bar_id_dict_id:创建" + symbol)
            self.current_bar_id_dict[symbol] = {}  # 创建外层字典
            # 创建内层字典
            self.current_bar_id_dict[symbol]['1m'] = -1
            self.current_bar_id_dict[symbol]['5m'] = -1
            self.current_bar_id_dict[symbol]['15m'] = -1
            self.current_bar_id_dict[symbol]['30m'] = -1
            self.current_bar_id_dict[symbol]['1h'] = -1
            self.current_bar_id[symbol]['d'] = -1
            return -1;

    ### 以上部分为解决当前K线不显示问题的代码⬆⬆⬆⬆⬆⬆⬆⬆⬆⬆⬆⬆⬆

    def get_bar_generator(self, vt_symbol: str) -> dict[str,CurrentBarGenerator]:#参数中:后是参数的类型的注释，python是动态类型的语言；->后是返回值类型的注释，说明返回一个BarGenerator类型
        """"""#这里就是取bg了#更换为CurrentBarGenerator
        #bg: Optional[BarGenerator] = self.bar_generators.get(vt_symbol, None)#在字典里取bg
        #原来的Bar生成器每个合约只对应一个，现在对应1m，5m，15m，30m，1h，1d，6个。
        bg_dict:dict[str,CurrentBarGenerator]=self.bar_generators.get(vt_symbol,None)#取某个合约的dict
        '''
        if not bg:#如果没有这个bg，则创建一个bg
            bg = BarGenerator(self.record_bar)#给bg的狗仔函数传入的是record_bar，其实就是一个空值：传入的是该函数的指针
            self.bar_generators[vt_symbol] = bg

        return bg
        '''
        #这里全部更换为CurrentGenerator
        if not bg_dict:#如果这个dict为空则创造一系列的Bar生成器
            bg_dict={}#创建空字典
            bg1m = CurrentBarGenerator( # 1分钟
                self.record_bar,
                1,
                self.record_bar,
                Interval.MINUTE,
                None,
                self.insert_current_bar,
                self.update_current_bar,
                self.delete_current_bar
                )
            bg_dict['1m']=bg1m
            bg5m = CurrentBarGenerator( # 5分钟
                self.record_bar,
                5,
                self.record_bar,
                Interval.MINUTE_5,
                None,
                self.insert_current_bar,
                self.update_current_bar,
                self.delete_current_bar
            )
            bg_dict['5m']=bg5m
            bg15m=CurrentBarGenerator( # 15分钟
                self.record_bar,
                15,
                self.record_bar,
                Interval.MINUTE_15,
                None,
                self.insert_current_bar,
                self.update_current_bar,
                self.delete_current_bar
            )
            bg_dict['15m']=bg15m
            bg30m=CurrentBarGenerator( # 30分钟
                self.record_bar,
                30,
                self.record_bar,
                Interval.MINUTE_30,
                None,
                self.insert_current_bar,
                self.update_current_bar,
                self.delete_current_bar
            )
            bg_dict['30m']=bg30m # 1小时
            bg1h=CurrentBarGenerator(
                self.record_bar,
                1,
                self.record_bar,
                Interval.HOUR,
                None,
                self.insert_current_bar,
                self.update_current_bar,
                self.delete_current_bar
            )
            bg_dict['1h']=bg1h
            day_end:time = time(hour=15,minute=00,second=00) #当日结束时间
            bg1d=CurrentBarGenerator( # 1日
                self.record_bar,
                1,
                self.record_bar,
                Interval.DAILY,
                day_end,
                self.insert_current_bar,
                self.update_current_bar,
                self.delete_current_bar
            )
            bg_dict['1d']=bg1d
            self.bar_generators[vt_symbol] = bg_dict
        return bg_dict

    def subscribe(self, contract: ContractData) -> None:
        """"""
        req: SubscribeRequest = SubscribeRequest(
            symbol=contract.symbol,
            exchange=contract.exchange
        )
        self.main_engine.subscribe(req, contract.gateway_name)

